/**
 * Copyright Dmitriy Likhten
 * Licensed under Apache License 2.0
 */
package dmitriylikhten.fileinjector;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
import java.util.Random;
import java.util.regex.Pattern;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

public class FileInjector extends Task {

    private static int LOG_LEVEL = Project.MSG_DEBUG; // For easy debugging...

    private File injectFrom = null;
    private File injectTo = null;
    private String lineToAppendAfter = null;
    private String commentStart = null;
    private String commentEnd = null;
    private boolean remove = false;

    private Project project = null;

    public void setProject(Project proj) {
        project = proj;
    }

    public Project getProject(){
        return project;
    }


    public File getInjectFrom() {
        return injectFrom;
    }

    public File getInjectTo() {
        return injectTo;
    }

    public String getLineToAppendAfter() {
        return lineToAppendAfter;
    }

    public String getCommentStart() {
        return commentStart;
    }

    public String getCommentEnd() {
        return commentEnd;
    }

    public boolean isRemove() {
        return remove;
    }

    public void setInjectFrom(File injectFrom) {
        this.injectFrom = injectFrom;
        log("Injecting from file: " + injectFrom.getAbsolutePath(), LOG_LEVEL);
    }

    public void setInjectTo(File injectTo) {
        this.injectTo = injectTo;
        log("Injecting into file: " + injectTo.getAbsolutePath(), LOG_LEVEL);
    }

    public void setLineToAppendAfter(String lineToAppendAfter) {
        this.lineToAppendAfter = lineToAppendAfter;
    }

    public void setCommentStart(String commentStart) {
        this.commentStart = commentStart;
        log("Comment start set to: " + commentStart, LOG_LEVEL);
    }

    public void setCommentEnd(String commentEnd) {
        this.commentEnd = commentEnd;
        log("Comment end set to: " + commentEnd, LOG_LEVEL);
    }

    /**
     * If true then instead of adding the injected code, it will remove a previous injection
     * @param remove
     */
    public void setRemove(boolean remove) {
        this.remove = remove;
    }

    /**
     * Execute! This will start the file operations...
     */
    public void execute() throws BuildException {
        super.execute();
        RandomAccessFile injectToRAF = null;
        BufferedReader injectToReader = null;
        BufferedWriter injectToWriter = null;
        BufferedReader injectFromReader = null;
        BufferedReader tempReader = null;
        BufferedWriter tempWriter = null;
        File tempFile = null;

        try {
            log("Original file's contents:",LOG_LEVEL);
            logInjectTo();

            injectToRAF = new RandomAccessFile(injectTo,"rw");
            injectToReader = new BufferedReader(new FileReader(injectToRAF.getFD()));
            injectFromReader = new BufferedReader(new FileReader(injectFrom));

            // Create a temp file to copy through... (this is our pipe file)
            Random r = new Random(System.currentTimeMillis());
            tempFile = File.createTempFile(String.valueOf(r.nextInt()),".tmp");
            tempWriter = new BufferedWriter(new FileWriter(tempFile));

            if(remove) {
                copyDestToTempRemoveInjections(injectToReader,tempWriter);
            }
            else {
                copyDestToTempAddInjections(injectToReader,injectFromReader,tempWriter);
            }

            // Copy the temp file we created into the dest file...
            // This will have the effect of the dest file being packed with new
            // data.
            //
            // Clear the destination file.
            injectToRAF.setLength(0L);
            injectToWriter = new BufferedWriter(new FileWriter(injectToRAF.getFD()));
            tempReader = new BufferedReader(new FileReader(tempFile));
            copyFile(tempReader, injectToWriter);

            log("Modified file's contents:",LOG_LEVEL);
            logInjectTo();
        }
        catch (Exception e) {
            throw new BuildException(e);
        }
        finally {
            close(injectToReader);
            close(injectToWriter);
            close(injectToRAF);
            close(injectFromReader);
            close(tempReader);
            close(tempWriter);
            if(tempFile != null){
                tempFile.delete();
            }
        }
    }

    /**
     * Log the entire injectTo file.
     * @throws IOException
     */
    protected void logInjectTo() throws IOException {
        BufferedReader destReader = null;
        try{
            destReader = new BufferedReader(new FileReader(injectTo));
            String line = destReader.readLine();
            while (line != null) {
                log(line,LOG_LEVEL);
                line = destReader.readLine();
            }
        }
        finally{
            close(destReader);
        }
    }

    /**
     * Write the entire dest file into the temp file. If the start comment is found, all
     * lines are ignored untill after the end comment.
     *
     * @param injectToReader The destination file.
     * @param tempWriter The temporary file to write to.
     * @throws IOException
     */
    protected void copyDestToTempRemoveInjections(BufferedReader injectToReader, BufferedWriter tempWriter)
    throws IOException {
        String startComment = createInjectionStartString(injectFrom);
        String endComment = createInjectionEndString(injectFrom);

        boolean writeToTemp = true;

        String line = injectToReader.readLine();
        while (line != null) {
            // If we found the start comment then stop writing
            if(line.equals(startComment)){
                writeToTemp = false;
            }
            if(writeToTemp) {
                // Write the line to temp...
                tempWriter.append(line);
                tempWriter.newLine();
                tempWriter.flush();
            }
            // If we found the end comment then start writing
            if(line.equals(endComment)){
                writeToTemp = true;
            }

            line = injectToReader.readLine();
        }
    }

    /**
     * Write the entire dest file into the temp file. If the {@link lineToAppendAfter} is
     * located, then the source is injected after that line.
     *
     * @param injectToReader The destination file.
     * @param injectFromReader The source to inject.
     * @param tempWriter The temporary file to write to.
     * @throws IOException
     */
    protected void copyDestToTempAddInjections(BufferedReader injectToReader, BufferedReader injectFromReader, BufferedWriter tempWriter)
    throws IOException {
        Pattern lookForLine = Pattern.compile(lineToAppendAfter);
        String line = injectToReader.readLine();
        while (line != null) {
            // Write the line to temp...
            tempWriter.append(line);
            tempWriter.newLine();
            tempWriter.flush();

            // Inject the source file if at the right line...
            log("Reading line " + line, LOG_LEVEL);
            if (lookForLine.matcher(line).find()) {
                log("Last line matched...", LOG_LEVEL);
                injectSource(tempWriter, injectFromReader);
            }
            line = injectToReader.readLine();
        }

    }

    /**
     * Injects all data from the injectFromReader into the injectToWriter's current position.
     * @param injectToWriter Where to inject data
     * @param injectFromReader Where to get data to be injected
     */
    protected void injectSource(BufferedWriter injectToWriter, BufferedReader injectFromReader)
    throws IOException {
        // Create the header
        injectToWriter.append(createInjectionStartString(injectFrom));
        injectToWriter.newLine();

        // Create the injection
        String line = injectFromReader.readLine();
        while (line != null) {
            injectToWriter.append(line);
            injectToWriter.newLine();
            line = injectFromReader.readLine();
        }
        // Create the footer
        injectToWriter.append(createInjectionEndString(injectFrom));
        injectToWriter.newLine();
        injectToWriter.flush();
    }

    /**
     * Copy the source file into the dest file.
     * @param source
     * @param dest
     * @throws IOException
     */
    protected void copyFile(BufferedReader source, BufferedWriter dest)
            throws IOException {
        String line = source.readLine();
        while (line != null) {
            dest.append(line);
            dest.newLine();
            line = source.readLine();
        }
        // Create the footer
        dest.flush();
    }

    /**
     * @param source
     * @return The start injection comment
     */
    protected String createInjectionStartString(File source) {
        return commentStart + " start_injection_" + source.getName()
                + ((commentEnd != null && commentEnd.length() > 0) ? " " + commentEnd : "");
    }

    /**
     * @param source
     * @return The end injection comment
     */
    protected String createInjectionEndString(File source) {
        return commentStart + " end_injection_" + source.getName()
                + ((commentEnd != null && commentEnd.length() > 0) ? " " + commentEnd : "");
    }

    protected void close(Reader r){
        try{
            if(r != null){
                r.close();
            }
        }
        catch(IOException e){
            // What are you gona do?
        }
    }

    protected void close(Writer w){
        try{
            if(w != null){
                w.close();
            }
        }
        catch(IOException e){
            // What are you gona do?
        }
    }

    protected void close(RandomAccessFile r){
        try{
            if(r != null){
                r.close();
            }
        }
        catch(IOException e){
            // What are you gona do?
        }
    }
}
